// Ofri Keidar 302933981 group 05

package controller;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class implements Client-Controller interface. The class connects to the server, sends user's queries to the server
 * and returns the queries' results 
 */
public class ConnectionManager implements ClientController {

	// connection details
	private static final String HOST = "localhost";
	private static final int PORT = 7000;
	private static final int SERVER_TIMEOUT = 2000; // how long client's socket waits in accept()
	
	private Socket socket; // used to connect to server
	private ObjectInputStream fromServerObjectStream; // used to read Message object from the server
	private ObjectOutputStream toServerObjectStream; // used to write Message object to the server 
		
	/**
	 * Constructor- creates a new object to manage the connection to the server
	 * @throws ServerException if had problems connecting to server
	 */
	public ConnectionManager() throws ServerException {		
		
		// connect to server
		try {
			
			System.out.println("Connecting to server...");
			socket = new Socket(HOST, PORT);
			socket.setSoTimeout(SERVER_TIMEOUT);
			System.out.println("Connection established!");
			
			// initialize streams with default null value
			fromServerObjectStream = null;
			toServerObjectStream = null;
			
		} catch (UnknownHostException e) {
	
			// problem in connection
			throw new ServerException("Error connecting to server!");
			
		} catch (IOException e) {
			
			// problem in connection
			throw new ServerException("Error connecting to server!");
		}
	}
	
	/**
	 * Gets all vacant flats from the server.
	 * Flats are sorted by total area in ascending order.
	 * Each flat is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public List<Map<String, String>> getVacantFlats() throws ServerException {

		// initialize message
		Message toServerMsg = new Message(Message.GET_VACANT_FLATS, new HashMap<String,String>());

		// write message to server
		sendMessage(toServerMsg); 
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}
	
	/**
	 * Gets vacant flats with given room number from the server.
	 * Flats are sorted by total area in ascending order.
	 * Each flat is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public List<Map<String, String>> getVacantFlats(double roomNum)
			throws ServerException {

		// initialize message
		Map<String,String> queryParams = new HashMap<String,String>();
		queryParams.put(Message.NUM_OF_ROOMS, ""+roomNum);
		Message toServerMsg = new Message(Message.GET_VACANT_FLATS_BY_ROOM, queryParams);

		// send message
		sendMessage(toServerMsg);
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}
	
	/**
	 * Gets vacant flats with given room number at given address from the server.
	 * Flats are sorted by total area in ascending order.
	 * Each flat is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public List<Map<String, String>> getVacantFlats(double roomNum,
			String stName, int bldNumber) throws ServerException {
		
		// initialize message
		Map<String,String> queryParams = new HashMap<String,String>();
		queryParams.put(Message.NUM_OF_ROOMS, ""+roomNum);
		queryParams.put(Message.ST_NAME, stName);
		queryParams.put(Message.BLD_NUMBER, ""+bldNumber);
		Message toServerMsg = new Message(Message.GET_VACANT_FLATS_BY_ROOM_AND_ADDRESS, queryParams);
		
		// send message
		sendMessage(toServerMsg);
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();				
	}

	/**
	 * Gets flats at given address from the server.
	 * Flats are sorted by total area in ascending order.
	 * Each flat is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem  
	 */
	@Override
	public List<Map<String, String>> getFlats(String stName, int bldNumber)
			throws ServerException {

		// initialize message
		Map<String,String> queryParams = new HashMap<String,String>();		
		queryParams.put(Message.ST_NAME, stName);
		queryParams.put(Message.BLD_NUMBER, ""+bldNumber);
		Message toServerMsg = new Message(Message.GET_FLATS_BY_ADDRESS, queryParams);
		
		// send message
		sendMessage(toServerMsg);
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}

	/**
	 * Gets all flats from the server.
	 * Flats are sorted by total area in ascending order.
	 * Each flat is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem  
	 */
	@Override
	public List<Map<String, String>> getFlats() throws ServerException {

		// initialize message
		Message toServerMsg = new Message(Message.GET_ALL_FLATS, new HashMap<String,String>());

		// write message to server
		sendMessage(toServerMsg); 
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}
	
	/**
	 * Gets all penthouses from the server.
	 * Penthouses are sorted by total area in ascending order.
	 * Each penthouse is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem  
	 */
	@Override
	public List<Map<String, String>> getPenthouses() throws ServerException {
				
		// initialize message
		Message toServerMsg = new Message(Message.GET_ALL_PENTHOUSES, new HashMap<String,String>());

		// write message to server
		sendMessage(toServerMsg); 
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}

	/**
	 * Gets all garden apartments from the server.
	 * Garden apartments are sorted by total area in ascending order.
	 * Each garden apartment is returned as a map of parameters
	 * Throws server exception if server connection encountered a problem  
	 */
	@Override
	public List<Map<String, String>> getGardenApartments()
			throws ServerException {
		
		// initialize message
		Message toServerMsg = new Message(Message.GET_ALL_GARDEN_APARTMENTS, new HashMap<String,String>());

		// write message to server
		sendMessage(toServerMsg); 
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return fromServerMsg.getMultipleParameters();
	}

	/**
	 * Asks the server for the vacant apartments, and returns number of elements in this list.
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public int getVacantAprtsNum() throws ServerException {
		
		// initialize message
		Message toServerMsg = new Message(Message.GET_VACANT_APARTMENTS_NUM,
				new HashMap<String, String>());

		// write message to server
		sendMessage(toServerMsg);

		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return Integer.parseInt(fromServerMsg.getMultipleParameters().get(0).get(Message.NUMERIC_VALUE));
	}
	
	/**
	 * Asks the server for the vacant apartments at given address, and returns number of elements in this list.
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public int getVacantAprtsNum(String stName, int bldNumber)
			throws ServerException {
		
		// initialize message
		Map<String,String> queryParams = new HashMap<String,String>();		
		queryParams.put(Message.ST_NAME, stName);
		queryParams.put(Message.BLD_NUMBER, ""+bldNumber);
		Message toServerMsg = new Message(Message.GET_VACANT_APARTMENTS_NUM_BY_ADDRESS, queryParams);

		// write message to server
		sendMessage(toServerMsg);

		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return Integer.parseInt(fromServerMsg.getMultipleParameters().get(0).get(Message.NUMERIC_VALUE));
	}
	
	/**
	 * Gets parameters of all apartments and new building's address. 
	 * If there already another building at given address, no building is created and False is returned. 
	 * Otherwise, creates a new building with given details and True is returned.
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public boolean addBuilding(List<Map<String, String>> parameterList,
			String stName, int bldNumber) throws ServerException {

		// wrap building address in a map
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put(Message.ST_NAME, stName);
		parameters.put(Message.BLD_NUMBER, ""+bldNumber);
		
		// initialize message
		Message toServerMsg = new Message(Message.ADD_BUILDINGS, parameters, parameterList);

		// write message to server
		sendMessage(toServerMsg);
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return Boolean.parseBoolean(fromServerMsg.getMultipleParameters().get(0).get(Message.BOOLEAN_VALUE));
	}
	
	/**
	 * Uses server to update resident name at apartment with given number at given address.
	 * Returns True if could update, False if not.
	 * Throws server exception if server connection encountered a problem 
	 */
	@Override
	public boolean updateResident(String stName, int bldNum, int apartmentNum,
			String newResident) throws ServerException {

		// wrap building address in a map
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put(Message.ST_NAME, stName);
		parameters.put(Message.BLD_NUMBER, ""+bldNum);
		parameters.put(Message.APARTMENT_NUMBER, ""+apartmentNum);
		parameters.put(Message.NEW_RESIDENT_NAME, newResident);
		
		// initialize message
		Message toServerMsg = new Message(Message.UPDATE_RESIDENT, parameters);
		
		// write message to server
		sendMessage(toServerMsg);
		
		// get server's answer
		Message fromServerMsg = getMessage();
		
		// return message's content
		return Boolean.parseBoolean(fromServerMsg.getMultipleParameters().get(0).get(Message.BOOLEAN_VALUE));
	}
	
	/**
	 * Shuts client system down by disconnecting the client from the server 
	 */
	@Override
	public void shutDown() throws ServerException {
		
		// disconnect from server
		try {
			socket.close();
		} catch (IOException e) {
			throw new ServerException("Error disconnecting from server!");
		}
	}
	
	/**
	 * Sends given Message object to server
	 * @param toServerMsg- message to write to server
	 * @throws ServerException if had problems connecting to server
	 */
	private void sendMessage(Message toServerMsg) throws ServerException {
		
		try {
		
			// get output stream to server
			toServerObjectStream = new ObjectOutputStream(socket.getOutputStream());
			
			// write Message object to server
			toServerObjectStream.writeObject(toServerMsg);
			
		} catch (SocketException e) {
			
			// remote side socket close
			throw new ServerException("Error in connection to server!");
			
		} catch (EOFException e) {
			
			System.out.println("ConnectionManager.sendMessage()");
			
			// throw server exception
			throw new ServerException("Error sending message to server!");
			
		} catch (IOException e) {
			
			// throw server exception
			throw new ServerException("Error sending message to server!");
		}
	}

	/**
	 * Reads Message object from server
	 * @return message read from server
	 * @throws ServerException if had problems connecting to server
	 */
	private Message getMessage() throws ServerException {

		try {
			
			// get input stream from server
			fromServerObjectStream = new ObjectInputStream(socket.getInputStream());
			
			// read Message object from server
			Message fromServerMsg = (Message) fromServerObjectStream.readObject();
			
			// return read message
			return fromServerMsg;
			
		} catch (SocketException e) {
			
			// remote side socket close
			throw new ServerException("Error in connection to server!");
			
		} catch (EOFException e) {
			
			// throw server exception
			throw new ServerException("Error sending message to server!");
			
		} catch (IOException e) {
			
			// throw server exception
			throw new ServerException("Error getting message from server!");			
			
		} catch (ClassNotFoundException e) {
			
			// throw server exception
			throw new ServerException("Error getting message from server server!");
		}
	}
}